/* 
 * Copyright (C) 2008 Torgny Bjers
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sugree.jibjib;

import java.util.List;
import java.util.ArrayList;

import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;

import org.json.JSONException;
import org.json.JSONObject;

import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.ContentUris;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.speech.RecognizerIntent;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.SimpleCursorAdapter;
import android.widget.ViewAnimator;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.sugree.jibjib.data.JibJibDatabase;
import com.sugree.jibjib.data.DirectMessages;
import com.sugree.jibjib.data.TweetBinder;
import com.sugree.jibjib.data.Tweet;
import com.sugree.jibjib.net.ConnectionAuthenticationException;
import com.sugree.jibjib.net.ConnectionException;
import com.sugree.jibjib.net.ConnectionUnavailableException;
import com.sugree.jibjib.util.RelativeTime;

/**
 * @author torgny.bjers
 * 
 */
public class MessageListActivity extends TimelineActivity {

	public static final String TAG = "JibJib";

	// Context menu items
	public static final int CONTEXT_MENU_ITEM_REPLY = Menu.FIRST + 3;
	public static final int CONTEXT_MENU_ITEM_STAR = Menu.FIRST + 4;
	public static final int CONTEXT_MENU_ITEM_DIRECT_MESSAGE = Menu.FIRST + 5;
	public static final int CONTEXT_MENU_ITEM_UNFOLLOW = Menu.FIRST + 6;
	public static final int CONTEXT_MENU_ITEM_BLOCK = Menu.FIRST + 7;
	public static final int CONTEXT_MENU_ITEM_RETWEET = Menu.FIRST + 8;
	public static final int CONTEXT_MENU_ITEM_PROFILE = Menu.FIRST + 9;
	public static final int CONTEXT_MENU_ITEM_REPLY_QUOTE = Menu.FIRST + 10;
	public static final int CONTEXT_MENU_ITEM_REPLY_ALL = Menu.FIRST + 11;

	public static final int REQUEST_CODE_VOICE_RECOGNITION = 1;

	// Views and widgets
	private Button mSpeechButton;
	private Button mSendButton;
	private EditText mEditText;
	private TextView mCharsLeftText;
	private ViewAnimator mFlipper;
	private DataAdapter mAdapter;

	// Text limits
	private int mCurrentChars = 0;
	private int mLimitChars = 140;
	private boolean mInitializing = false;
	private boolean mRequerying = false;

	// Database cursors
	//private Cursor mFriendsCursor;

	protected String mUsername;
	protected long mReplyId = 0;
	protected long mCurrentId = 0;

	// Table columns to use for the direct messages data
	private static final String[] PROJECTION = new String[] {
		JibJibDatabase.DirectMessages._ID,
		JibJibDatabase.DirectMessages.AUTHOR_ID,
		JibJibDatabase.DirectMessages.MESSAGE,
		JibJibDatabase.DirectMessages.PROTECTED,
		JibJibDatabase.DirectMessages.CREATED_DATE,
		JibJibDatabase.DirectMessages.SENT_DATE
	};

	// Table columns to use for the user data
	/*
	private static final String[] FRIENDS_PROJECTION = new String[] {
		Users._ID,
		Users.AUTHOR_ID
	};
	*/

	/**
	 * Called when the activity is first created.
	 * 
	 * @see android.app.Activity#onCreate(android.os.Bundle)
	 * 
	 * @param savedInstanceState
	 */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		super.setHandler(mHandler);

		if (savedInstanceState != null) {
			if (savedInstanceState.containsKey(BUNDLE_KEY_CURRENT_PAGE)) {
				mCurrentPage = savedInstanceState.getInt(BUNDLE_KEY_CURRENT_PAGE);
			}
			if (savedInstanceState.containsKey(BUNDLE_KEY_IS_LOADING)) {
				mIsLoading = savedInstanceState.getBoolean(BUNDLE_KEY_IS_LOADING);
			}
		}

		final Intent intent = getIntent();
        final String queryAction = intent.getAction();
		if (intent.getData() == null) {
			intent.setData(JibJibDatabase.DirectMessages.CONTENT_URI);
		}
        if (Intent.ACTION_SEARCH.equals(queryAction)) {
            //doSearchQuery(intent, "onCreate()");
        }

		// Set up views
		mSpeechButton = (Button) findViewById(R.id.speechButton);
		mSendButton = (Button) findViewById(R.id.messageEditSendButton);
		mEditText = (EditText) findViewById(R.id.edtTweetInput);
		mCharsLeftText = (TextView) findViewById(R.id.messageEditCharsLeftTextView);
		mFlipper = (ViewAnimator) findViewById(R.id.tweetlist_flipper);

		// Create list footer for loading messages
		mListFooter = new LinearLayout(getApplicationContext());
		mListFooter.setClickable(false);
		getListView().addFooterView(mListFooter);
		LayoutInflater inflater = LayoutInflater.from(getApplicationContext());
		View tv = inflater.inflate(R.layout.item_loading, null);
		mListFooter.addView(tv, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
		mListFooter.setVisibility(View.INVISIBLE);

		getListView().setOnScrollListener(this);

		initUI();

		int listItemId = R.layout.messagelist_item;
		if (mSP.getBoolean("appearance_use_avatars", false)) {
			listItemId = R.layout.messagelist_item_avatar;
		}
		mAdapter = new DataAdapter(this, listItemId, JibJibDatabase.DirectMessages.CONTENT_URI, JibJibDatabase.DirectMessages.DEFAULT_SORT_ORDER);
		setListAdapter(mAdapter);

		newQuery(JibJibDatabase.DirectMessages.CONTENT_URI,
				JibJibDatabase.DirectMessages.DEFAULT_SORT_ORDER+" LIMIT 0,"+(mCurrentPage*mPageSize));

		mNM.cancel(JibJibService.NOTIFY_DIRECT_MESSAGES);
	}

	@Override
	public boolean onSearchRequested() {
	    Bundle appDataBundle = new Bundle();
	    appDataBundle.putParcelable("content_uri", JibJibDatabase.DirectMessages.CONTENT_URI);
		startSearch(null, false, appDataBundle, false);
		return true;
	}

	@Override
	protected void onResume() {
		super.onResume();
		mCharsLeftText.setText(String.valueOf(mLimitChars - mEditText.length()));
		requery();
	}

	@Override
	protected void onRestart() {
		super.onRestart();
		mAdapter.updateSortOrder();
	}

	@Override
	protected void onStart() {
		super.onStart();
		mUsername = mSP.getString("twitter_username", null);
		setProgressBarIndeterminateVisibility(mIsLoading);
		mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_BIND_SERVICE, 0, 0), 500);
	}

	@Override
	protected void onStop() {
		super.onStop();
		disconnectService();
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		disconnectService();
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		outState.putInt(BUNDLE_KEY_CURRENT_PAGE, mCurrentPage);
		outState.putBoolean(BUNDLE_KEY_IS_LOADING, mIsLoading);
		super.onSaveInstanceState(outState);
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
		case R.id.reload_menu_item:
			startManualReload();
			break;
		}
		return super.onOptionsItemSelected(item);
	}

	@Override
	public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
		super.onCreateContextMenu(menu, view, menuInfo);
		// Get the adapter context menu information
		AdapterView.AdapterContextMenuInfo info;
		try {
			info = (AdapterView.AdapterContextMenuInfo) menuInfo;
		} catch (ClassCastException e) {
			Log.e(TAG, "bad menuInfo", e);
			return;
		}

		int m = 0;

		// Add menu items
		//menu.add(0, CONTEXT_MENU_ITEM_STAR, m++, R.string.menu_item_star);
		menu.add(0, CONTEXT_MENU_ITEM_DIRECT_MESSAGE, m++, R.string.menu_item_direct_message);
		menu.add(0, CONTEXT_MENU_ITEM_REPLY, m++, R.string.menu_item_reply);
		menu.add(0, CONTEXT_MENU_ITEM_RETWEET, m++, R.string.menu_item_retweet);
		menu.add(0, CONTEXT_MENU_ITEM_REPLY_ALL, m++, R.string.menu_item_reply_all);
		menu.add(0, CONTEXT_MENU_ITEM_REPLY_QUOTE, m++, R.string.menu_item_reply_quote);
		//menu.add(0, CONTEXT_MENU_ITEM_PROFILE, m++, R.string.menu_item_view_profile);
		//menu.add(0, CONTEXT_MENU_ITEM_UNFOLLOW, m++, R.string.menu_item_unfollow);
		//menu.add(0, CONTEXT_MENU_ITEM_BLOCK, m++, R.string.menu_item_block);

		// Get the record for the currently selected item
		Uri uri = ContentUris.withAppendedId(JibJibDatabase.DirectMessages.CONTENT_URI, info.id);
		Cursor c = getContentResolver().query(uri, PROJECTION, null, null, null);
		try {
			c.moveToFirst();
			menu.setHeaderTitle(c.getString(c.getColumnIndex(JibJibDatabase.DirectMessages.MESSAGE)));
		} catch (Exception e) {
			Log.e(TAG, e.getMessage());
		} finally {
			if (c != null && !c.isClosed()) c.close();
		}
	}

	@Override
	public boolean onContextItemSelected(MenuItem item) {
		super.onContextItemSelected(item);
		AdapterView.AdapterContextMenuInfo info;
		try {
			info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
		} catch (ClassCastException e) {
			Log.e(TAG, "bad menuInfo", e);
			return false;
		}

		switch (item.getItemId()) {
		case CONTEXT_MENU_ITEM_REPLY:
		case CONTEXT_MENU_ITEM_REPLY_ALL:
		case CONTEXT_MENU_ITEM_REPLY_QUOTE:
		case CONTEXT_MENU_ITEM_DIRECT_MESSAGE:
		case CONTEXT_MENU_ITEM_RETWEET:
			Uri uri = ContentUris.withAppendedId(JibJibDatabase.DirectMessages.CONTENT_URI, info.id);
			Cursor c = getContentResolver().query(uri, PROJECTION, null, null, null);
			try {
				c.moveToFirst();
				if (hasHardwareKeyboard()) {
					mEditText.requestFocus();
				}
				Tweet tweet = new Tweet(
						mSP.getString("twitter_username", null),
						c.getString(c.getColumnIndex(JibJibDatabase.DirectMessages.AUTHOR_ID)),
						c.getString(c.getColumnIndex(JibJibDatabase.DirectMessages.MESSAGE)));
				String reply = "";
				if (item.getItemId() == CONTEXT_MENU_ITEM_DIRECT_MESSAGE) {
					reply = tweet.replyDirectMessage();
				} else if (item.getItemId() == CONTEXT_MENU_ITEM_REPLY) {
					reply = tweet.reply();
				} else if (item.getItemId() == CONTEXT_MENU_ITEM_REPLY_ALL) {
					reply = tweet.replyAll();
				} else if (item.getItemId() == CONTEXT_MENU_ITEM_REPLY_QUOTE) {
					reply = tweet.replyQuote();
				} else if (item.getItemId() == CONTEXT_MENU_ITEM_RETWEET) {
					reply = tweet.retweet();
				}

				mEditText.setText("");
				mEditText.append(reply, 0, reply.length());
				mReplyId = c.getLong(c.getColumnIndex(JibJibDatabase.DirectMessages._ID));
				mFlipper.setDisplayedChild(0);
			} catch (Exception e) {
				Log.e(TAG, e.getMessage());
			} finally {
				if (c != null && !c.isClosed()) c.close();
			}
			return true;

		case CONTEXT_MENU_ITEM_STAR:
		case CONTEXT_MENU_ITEM_UNFOLLOW:
		case CONTEXT_MENU_ITEM_BLOCK:
		case CONTEXT_MENU_ITEM_PROFILE:
			Toast.makeText(this, R.string.unimplemented, Toast.LENGTH_SHORT).show();
			return true;
		}
		return false;
	}

	void requery() {
		if (mIsForeground && !mHandler.hasMessages(MSG_REQUERY)) {
			mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_REQUERY, 0, 0), 50);
		}
	}

	void newQuery(Uri uri, String sortOrder) {
		mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_NEW_QUERY, new NewQuery(uri, sortOrder)), 50);
	}

	/**
	 * Send the message.
	 */
	void sendMessage() {
		String msg = mEditText.getText().toString();
		if (TextUtils.isEmpty(msg.trim())) {
			Toast.makeText(MessageListActivity.this, R.string.cannot_send_empty_message, Toast.LENGTH_SHORT).show();
		} else {
			showDialog(DIALOG_SENDING_MESSAGE);
			Thread thread = new Thread(mSendMessage);
			thread.start();
			closeSoftKeyboard();
		}
	}

	/**
	 * Close the on-screen keyboard.
	 */
	private void closeSoftKeyboard() {
		InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
		inputMethodManager.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
	}

	/**
	 * Initialize UI
	 */
	@Override
	protected void initUI() {
		super.initUI();

		((Button)findViewById(R.id.nextButton1)).setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				mFlipper.showNext();
			}
		});

		((Button)findViewById(R.id.nextButton2)).setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				mFlipper.showPrevious();
			}
		});

		((Button)findViewById(R.id.refreshButton)).setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				startManualReload();
			}
		});

		((Button)findViewById(R.id.friendButton)).setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				showFriendTimeline();
			}
		});

		((Button)findViewById(R.id.mentionButton)).setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				showMentionTimeline();
			}
		});

		((Button)findViewById(R.id.messageButton)).setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				showMessageTimeline();
			}
		});

		((Button)findViewById(R.id.topButton)).setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				goToTop();
			}
		});

		((Button)findViewById(R.id.tweetButton)).setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				sendTweet();
			}
		});

		mSpeechButton.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				startVoiceRecognitionActivity();
			}
		});

		mSendButton.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				sendMessage();
			}
		});

		mEditText.addTextChangedListener(new TextWatcher() {
			public void afterTextChanged(Editable s) {
				mCurrentChars = s.length();
				mCharsLeftText.setText(String.valueOf(mLimitChars - mCurrentChars));
			}

			public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
			public void onTextChanged(CharSequence s, int start, int before, int count) {}
		});

		mEditText.setOnKeyListener(new View.OnKeyListener() {
			public boolean onKey(View v, int keyCode, KeyEvent event) {
				if (event.getAction() == KeyEvent.ACTION_DOWN) {
					mCurrentChars = mEditText.length();
					if (mCurrentChars == 0) {
						mReplyId = 0;
					}
					switch (keyCode) {
					case KeyEvent.KEYCODE_DPAD_CENTER:
						sendMessage();
						return true;
					case KeyEvent.KEYCODE_ENTER:
						if (event.isAltPressed()) {
							mEditText.append("\n");
							return true;
						}
					default:
						if (keyCode != KeyEvent.KEYCODE_DEL && mCurrentChars > mLimitChars) {
							return true;
						}
						mCharsLeftText.setText(String.valueOf(mLimitChars - mCurrentChars));
						break;
					}
				}
				return false;
			}
		});

		mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
			public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
				if (event != null) {
					if (event.isAltPressed()) {
						return false;
					}
					if (event.getAction() == event.ACTION_DOWN) {
						sendMessage();
						return true;
					}
				}
				return false;
			}
		});
	}

	protected void startManualReload() {
		setProgressBarIndeterminateVisibility(true);
		mListFooter.setVisibility(View.VISIBLE);
		Thread thread = new Thread(mManualReload);
		thread.start();
	}

	@Override
	protected void sendTweet() {
		final Intent intent = new Intent(MessageListActivity.this, SendTweet.class);
		Bundle appDataBundle = new Bundle();
		appDataBundle.putLong("reply_id", mReplyId);

		intent.putExtra("com.sugree.jibjib.app_data", appDataBundle);
		intent.putExtra(Intent.EXTRA_SUBJECT, getSavedText().toString());
		intent.setAction(Intent.ACTION_SEND);
		startActivity(intent);
	}

	private void startVoiceRecognitionActivity() {
		Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
		intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
		intent.putExtra("calling_package", "com.sugree.jibjib.MessageListActivity");
		//intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "Tips:\n 1) Speak clearly into the Mic.\n 2) Pause between words.");
		try {
			startActivityForResult(intent, REQUEST_CODE_VOICE_RECOGNITION);
		} catch (ActivityNotFoundException e) {
			Toast.makeText(MessageListActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
			Log.d(TAG, e.getMessage());
		}
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		Log.d(TAG, "ActivityResult "+REQUEST_CODE_VOICE_RECOGNITION+" "+RESULT_OK);
		Log.d(TAG, "ActivityResult "+requestCode+" "+resultCode);
		if (requestCode == REQUEST_CODE_VOICE_RECOGNITION && resultCode == RESULT_OK) {
			ArrayList<String> matches = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
			Log.d(TAG, (String)matches.get(0));
			mEditText.getEditableText().replace(mEditText.getSelectionStart(), mEditText.getSelectionEnd(), (String)matches.get(0));
			mFlipper.setDisplayedChild(0);
		}
		super.onActivityResult(requestCode, resultCode, data);
	}

	/*
	private void createAdapters() {
		int listItemId = R.layout.messagelist_item;
		if (mSP.getBoolean("appearance_use_avatars", false)) {
			listItemId = R.layout.messagelist_item_avatar;
		}
		mAdapter = new PagedCursorAdapter(
			MessageListActivity.this,
			listItemId,
			mCursor,
			new String[] { JibJibDatabase.DirectMessages.AUTHOR_ID, JibJibDatabase.DirectMessages.MESSAGE, JibJibDatabase.DirectMessages.SENT_DATE },
			new int[] { R.id.tweet_screen_name, R.id.tweet_message, R.id.tweet_sent },
			JibJibDatabase.DirectMessages.CONTENT_URI,
			PROJECTION,
			JibJibDatabase.DirectMessages.DEFAULT_SORT_ORDER
		);
		mAdapter.setViewBinder(new TweetBinder());

		setListAdapter(mAdapter);
	}
	*/

	/**
	 * Listener that checks for clicks on the main list view.
	 * 
	 * @param adapterView
	 * @param view
	 * @param position
	 * @param id
	 */
	@Override
	public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
		Tweet t = mAdapter.getItem(position);
		if (t == null) {
			return;
		}
		Uri uri = ContentUris.withAppendedId(JibJibDatabase.DirectMessages.CONTENT_URI, t.getId());
		String action = getIntent().getAction();
		if (Intent.ACTION_PICK.equals(action) || Intent.ACTION_GET_CONTENT.equals(action)) {
			setResult(RESULT_OK, new Intent().setData(uri));
		} else {
			startActivity(new Intent(Intent.ACTION_VIEW, uri));
		}
	}

	@Override
	public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
		mTotalItemCount = totalItemCount;
	}

	@Override
	public void onScrollStateChanged(AbsListView view, int scrollState) {
		if (mIsLoading) {
			return;
		}
		switch (scrollState) {
		case SCROLL_STATE_IDLE:
			if (view.getLastVisiblePosition() >= mTotalItemCount - 1 && mTotalItemCount > 0) {
				if (getListView().getFooterViewsCount() == 1 && !mIsLoading) {
					mIsLoading = true;
					setProgressBarIndeterminateVisibility(true);
					mListFooter.setVisibility(View.VISIBLE);
					Thread thread = new Thread(mLoadListItems);
					thread.start();
				}
			}
			break;
		case SCROLL_STATE_TOUCH_SCROLL:
			break;
		case SCROLL_STATE_FLING:
			break;
		}
	}

	/**
	 * Message handler for messages from threads.
	 */
	protected Handler mHandler = new Handler() {
		/**
		 * Message handler
		 * 
		 * @param msg
		 */
		@Override
		public void handleMessage(Message msg) {
			JSONObject result = null;
			String username = mSP.getString("twitter_username", null);
			String password = mSP.getString("twitter_password", null);
			switch (msg.what) {
			case MSG_BIND_SERVICE:
				bindToService();
				break;

			case MSG_DIRECT_MESSAGES_CHANGED:
				int numTweets = msg.arg1;
				if (numTweets > 0) {
					mNM.cancelAll();
				}
				requery();
				break;

			case MSG_REQUERY:
				synchronized (this) {
					if (!mRequerying) {
						mRequerying = true;
						Thread t = new Thread(mRequery);
						t.start();
					}
					removeMessages(MSG_REQUERY);
				}
				break;

			case MSG_NEW_QUERY:
				removeMessages(MSG_REQUERY);
				NewQuery o = (NewQuery)msg.obj;
				o.run();
				break;

			case MSG_NEW_ITEMS:
				mAdapter.newItems((List<Tweet>)msg.obj, msg.arg2 == 1);
				if (msg.arg1 == 1) {
					synchronized (this) {
						mRequerying = false;
						removeMessages(MSG_REQUERY);
					}
				}

			case MSG_DATA_LOADING:
				mIsLoading = msg.arg1 == 1 ? true : false;
		        setProgressBarIndeterminateVisibility(mIsLoading);
				break;

			case MSG_UPDATE_STATUS:
				result = (JSONObject) msg.obj;
				if (result.optString("error").length() > 0) {
					Toast.makeText(MessageListActivity.this, (CharSequence) result.optString("error"), Toast.LENGTH_LONG).show();
				} else {
					Toast.makeText(MessageListActivity.this, R.string.message_sent, Toast.LENGTH_SHORT).show();
					mReplyId = 0;
					mEditText.setText("");
					if (hasHardwareKeyboard()) {
						mEditText.requestFocus();
					}
				}
				try {
					dismissDialog(DIALOG_SENDING_MESSAGE);
				} catch (IllegalArgumentException e) {
					Log.d(TAG, e.getMessage());
				}
				break;

			case MSG_AUTHENTICATION_ERROR:
				switch (msg.arg1) {
				case MSG_MANUAL_RELOAD:
					setProgressBarIndeterminateVisibility(false);
					break;
				case MSG_UPDATE_STATUS:
					try {
						dismissDialog(DIALOG_SENDING_MESSAGE);
					} catch (IllegalArgumentException e) {
						Log.d(TAG, e.getMessage());
					}
					break;
				}
				showDialog(DIALOG_AUTHENTICATION_FAILED);
				break;

			case MSG_SERVICE_UNAVAILABLE_ERROR:
				switch (msg.arg1) {
				case MSG_MANUAL_RELOAD:
					setProgressBarIndeterminateVisibility(false);
					break;
				case MSG_UPDATE_STATUS:
					try {
						dismissDialog(DIALOG_SENDING_MESSAGE);
					} catch (IllegalArgumentException e) {
						Log.d(TAG, e.getMessage());
					}
					break;
				}
				showDialog(DIALOG_SERVICE_UNAVAILABLE);
				break;

			case MSG_MANUAL_RELOAD:
				mIsLoading = false;
				Toast.makeText(MessageListActivity.this, R.string.timeline_reloaded, Toast.LENGTH_SHORT).show();
				setProgressBarIndeterminateVisibility(false);
				break;

			case MSG_LOAD_ITEMS:
				switch (msg.arg1) {
				case STATUS_LOAD_ITEMS_SUCCESS:
					mIsLoading = false;
					mListFooter.setVisibility(View.INVISIBLE);
					if (mCursor != null) {
						try {
							mCursor.close();
						} catch (Exception e) {
							Log.e(TAG, e.toString());
						}
					}
					/*
					mCursor = mNewCursor;
					mNewCursor = null;
					((SimpleCursorAdapter) getListAdapter()).changeCursor(mCursor);
					*/
					mAdapter.newItems((List<Tweet>)msg.obj, true);
					setProgressBarIndeterminateVisibility(false);
					break;
				case STATUS_LOAD_ITEMS_FAILURE:
					break;
				}
				break;

			case MSG_CONNECTION_TIMEOUT_EXCEPTION:
				switch (msg.arg1) {
				case MSG_MANUAL_RELOAD:
					setProgressBarIndeterminateVisibility(false);
					break;
				case MSG_UPDATE_STATUS:
					try {
						dismissDialog(DIALOG_SENDING_MESSAGE);
					} catch (IllegalArgumentException e) {
						Log.d(TAG, e.getMessage());
					}
					break;
				}
				showDialog(DIALOG_CONNECTION_TIMEOUT);
				break;

			default:
				super.handleMessage(msg);
			}
		}
	};

	protected Runnable mRequery = new Runnable() {
		public void run() {
			mAdapter.requery();
		}
	};

	/**
	 * Handles threaded sending of messages.
	 */
	protected Runnable mSendMessage = new Runnable() {
		public void run() {
			String message = mEditText.getText().toString();
			/*
			String username = mSP.getString("twitter_username", null);
			String password = mSP.getString("twitter_password", null);
			com.sugree.jibjib.net.Connection aConn = new com.sugree.jibjib.net.Connection(username, password);
			*/
			com.sugree.jibjib.net.Connection aConn = new com.sugree.jibjib.net.Connection(mSP);
			JSONObject result = new JSONObject();
			try {
				result = aConn.updateStatus(message.trim(), mReplyId);
			} catch (UnsupportedEncodingException e) {
				Log.e(TAG, e.getMessage());
				mHandler.sendMessage(mHandler.obtainMessage(MSG_SERVICE_UNAVAILABLE_ERROR, MSG_UPDATE_STATUS, 0));
			} catch (ConnectionException e) {
				Log.e(TAG, "mSendMessage Connection Exception: " + e.getMessage());
				mHandler.sendMessage(mHandler.obtainMessage(MSG_SERVICE_UNAVAILABLE_ERROR, MSG_UPDATE_STATUS, 0));
				return;
			} catch (ConnectionAuthenticationException e) {
				mHandler.sendMessage(mHandler.obtainMessage(MSG_AUTHENTICATION_ERROR, MSG_UPDATE_STATUS, 0));
				return;
			} catch (ConnectionUnavailableException e) {
				mHandler.sendMessage(mHandler.obtainMessage(MSG_SERVICE_UNAVAILABLE_ERROR, MSG_UPDATE_STATUS, 0));
				return;
			} catch (SocketTimeoutException e) {
				mHandler.sendMessage(mHandler.obtainMessage(MSG_CONNECTION_TIMEOUT_EXCEPTION, MSG_UPDATE_STATUS, 0));
				return;
			}
			mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_STATUS, result));
		}
	};
	/**
	 * Handles threaded manual reload of the timeline.
	 */
	protected Runnable mManualReload = new Runnable() {
		public void run() {
			mIsLoading = true;
			String username = mSP.getString("twitter_username", null);
			String password = mSP.getString("twitter_password", null);
			DirectMessages directMessages = new DirectMessages(getContentResolver(), username, password, mSP.getLong("last_messages_runtime", System.currentTimeMillis()));
			directMessages.setGateway(mSP.getString("twitter_gateway", null));
			int aNewMessages = 0;
			try {
				directMessages.loadMessages(mSP);
				aNewMessages = directMessages.newCount();
			} catch (ConnectionException e) {
				Log.e(TAG, "mManualReload Connection Exception: " + e.getMessage());
				mHandler.sendMessage(mHandler.obtainMessage(MSG_AUTHENTICATION_ERROR, MSG_MANUAL_RELOAD, 0));
			} catch (SQLiteConstraintException e) {
				Log.e(TAG, "mManualReload database exception: " + e.getMessage());
				mHandler.sendMessage(mHandler.obtainMessage(MSG_AUTHENTICATION_ERROR, MSG_MANUAL_RELOAD, 0));
			} catch (JSONException e) {
				Log.e(TAG, "mManualReload JSON exception: " + e.getMessage());
				mHandler.sendMessage(mHandler.obtainMessage(MSG_AUTHENTICATION_ERROR, MSG_MANUAL_RELOAD, 0));
			} catch (ConnectionAuthenticationException e) {
				mHandler.sendMessage(mHandler.obtainMessage(MSG_AUTHENTICATION_ERROR, MSG_MANUAL_RELOAD, 0));
				return;
			} catch (ConnectionUnavailableException e) {
				mHandler.sendMessage(mHandler.obtainMessage(MSG_AUTHENTICATION_ERROR, MSG_MANUAL_RELOAD, 0));
				return;
			} catch (SocketTimeoutException e) {
				mHandler.sendMessage(mHandler.obtainMessage(MSG_CONNECTION_TIMEOUT_EXCEPTION, MSG_MANUAL_RELOAD, 0));
				return;
			}
			mHandler.sendMessage(mHandler.obtainMessage(MSG_MANUAL_RELOAD, aNewMessages, 0));
		}
	};

	/**
	 * Load more items into the list.
	 */
	protected Runnable mLoadListItems  = new Runnable() {
		public void run() {
			List<Tweet> items = mAdapter.runQuery("LIMIT 0," + (++mCurrentPage * 20));
			mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_LOAD_ITEMS, STATUS_LOAD_ITEMS_SUCCESS, 0, items), 400);
		}
	};

	private class NewQuery {
		private Uri mUri;
		private String mSortOrder;

		public NewQuery(Uri uri, String sortOrder) {
			mUri = uri;
			mSortOrder = sortOrder;
		}

		public void run() {
			mAdapter.newQuery(mUri, mSortOrder);
		}
	};

	private class DataAdapter extends BaseAdapter {
		private List<Tweet> mItems;
		private final Object mLock = new Object();
		private int mResId;
		private List<Tweet> mOriginalValues;
		private boolean mNotifyOnChange = true;

		private LayoutInflater mInflater;
		private Context mContext;
		private ListView mListView;
		private int mTop;
		private int mPosition;

		private Uri mUri;
		private String mSortOrder;

		public DataAdapter(Context context, int resId, Uri uri, String sortOrder) {
			mContext = context;
			mInflater = LayoutInflater.from(context);
			mListView = getListView();
			mResId = resId;
			mUri = uri;
			mSortOrder = sortOrder;

			mItems = new ArrayList<Tweet>();
			mOriginalValues = null;
		}

		public void setUri(Uri uri) {
			mUri = uri;
		}

		public void setSortOrder(String sortOrder) {
			mSortOrder = sortOrder;
		}

		public void setItems(List<Tweet> items) {
			if (mOriginalValues != null) {
				synchronized (mLock) {
					mOriginalValues = items;
					if (mNotifyOnChange) notifyDataSetChanged();
				}
			} else {
				mItems = items;
				if (mNotifyOnChange) notifyDataSetChanged();
			}
		}

		public void clear() {
			if (mOriginalValues != null) {
				synchronized (mLock) {
					mOriginalValues.clear();
				}
			} else {
				mItems.clear();
			}
			if (mNotifyOnChange) notifyDataSetChanged();
		}

		public void updateSortOrder() {
			int i = mSortOrder.indexOf("LIMIT 0,");
			if (i > 0) {
				mSortOrder = mSortOrder.substring(0, i);
			}
			mSortOrder += "LIMIT 0,"+(mCurrentPage*mPageSize);
		}

		public void setNotifyOnChange(boolean notifyOnChange) {
			mNotifyOnChange = notifyOnChange;
		}

		public int getCount() {
			return mItems.size();
		}

		public Tweet getItem(int pos) {
			return mItems.get(pos);
		}

		public int getPosition(Tweet item) {
			return mItems.indexOf(item);
		}

		public long getItemId(int position) {
			return mItems.get(position).getId();
		}

		@Override
		public void notifyDataSetChanged() {
			super.notifyDataSetChanged();
		}

		public List<Tweet> runQuery(CharSequence constraint) {
			if (constraint != null) {
				int i = mSortOrder.indexOf("LIMIT 0,");
				if (i > 0) {
					mSortOrder = mSortOrder.substring(0, i);
				}
				mSortOrder += " "+constraint.toString().trim();
			}
			Cursor c = managedQuery(mUri, PROJECTION, null, null, mSortOrder);
			return savePosition(c);
		}

		public void requery() {
			Log.d(TAG, "requery "+mUri+" "+mSortOrder);
			Cursor c = managedQuery(
				mUri,
				PROJECTION,
				null,
				null,
				mSortOrder);
			//newByCursor(c, true);
			List<Tweet> items = savePosition(c);
			mHandler.sendMessage(mHandler.obtainMessage(MSG_NEW_ITEMS, 1, 1, items));
		}

		public void newQuery(Uri uri, String sortOrder) {
			Log.d(TAG, "newQuery("+uri+","+sortOrder+")");
			mUri = uri;
			mSortOrder = sortOrder;
			Cursor c = managedQuery(
				uri,
				PROJECTION,
				null,
				null,
				sortOrder);
			//newByCursor(c, false);
			List<Tweet> items = savePosition(c);
			mHandler.sendMessage(mHandler.obtainMessage(MSG_NEW_ITEMS, 0, 0, items));
		}

		private List<Tweet> savePosition(Cursor c) {
			int first = mListView.getFirstVisiblePosition();
			int top = 0;
			View v = null;
			for(int i=0; i<2; i++, first++) {
				v = mListView.getChildAt(i);
				if (v == null) {
					break;
				};
				top = v.getTop();
				if (top >= 0) {
					break;
				}
			}
			long firstId = -1;
			if (v != null && v.getTag() != null) {
				firstId = ((ViewHolder)v.getTag()).tweet.getId();
			}

			List<Tweet> items = new ArrayList<Tweet>();
			int index = -1;
			for(int i=0; c.moveToNext(); i++) {
				Tweet t = Tweet.fetchOneMessage(mUsername, c);
				items.add(t);
				if (t.getId() == firstId) {
					index = i;
				}
			}
			if (index < 0) {
				index = getCount()-1;
			}
			c.close();

			mPosition = index;
			mTop = top;

			return items;
		}

		private void restorePosition(boolean fixPos) {
			if (fixPos) {
				mListView.setSelectionFromTop(mPosition, mTop);
			} else {
				mListView.setSelection(0);
			}
		}

		public void newItems(List<Tweet> items, boolean fixPos) {
			if (items.size() > 0 && mItems.size() > 0 && items.get(0).equals(mItems.get(0))) {
				return;
			}
			setItems(items);
			notifyDataSetChanged();
			restorePosition(fixPos);
		}

		@Override
		public View getView(int pos, View view, ViewGroup parent) {
			ViewHolder holder;

			if (view == null) {
				view = mInflater.inflate(mResId, null);

				holder = new ViewHolder();
				holder.screenName = (TextView)view.findViewById(R.id.tweet_screen_name);
				holder.message = (TextView)view.findViewById(R.id.tweet_message);
				holder.sent = (TextView)view.findViewById(R.id.tweet_sent);

				view.setTag(holder);
			} else {
				holder = (ViewHolder)view.getTag();
			}

			Tweet t = getItem(pos);
			holder.tweet = t;

			holder.screenName.setText(t.getScreenName());
			holder.message.setText(t.getText());

			String time = RelativeTime.getDifference(view.getContext(), t.getSentDate());
			holder.sent.setText(time);

			return view;
		}
	}

	private class ViewHolder {
		TextView screenName;
		TextView message;
		TextView sent;
		Tweet tweet;
	}
}
